// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//


#include <turbo/log/logging.h>
#include <gtest/gtest.h>
#include <thread>

#include <pollux/common/time/cpu_wall_timer.h>

using namespace kumo::pollux;

namespace kumo::pollux::test {

class CpuWallTimerTest : public testing::Test {
 protected:
  static void workAndSleep(std::chrono::nanoseconds sleepDuration) {
    // Use some cpu here.
    size_t n{0};
    for (size_t i = 0; i < 10'000; ++i) {
      n += 5 + i * 10;
    }

    // Condition to use variable 'n' otherwise the compiler might opt out the
    // computation loop above since 'n' is not used.
    if (n >= 5) {
      /* sleep override */
      std::this_thread::sleep_for(sleepDuration);
    }
  }
};

TEST_F(CpuWallTimerTest, cpuWallTiming) {
  const CpuWallTiming timingZero;
  EXPECT_EQ(0, timingZero.count);
  EXPECT_EQ(0, timingZero.wallNanos);
  EXPECT_EQ(0, timingZero.cpuNanos);

  constexpr uint64_t count11{11};
  constexpr uint64_t wallNanos11{1'500'345'000};
  constexpr uint64_t cpuNanos11{345'000};
  constexpr uint64_t count17{17};
  constexpr uint64_t wallNanos17{1'500'345'000};
  constexpr uint64_t cpuNanos17{6'789'000};

  CpuWallTiming timing11{count11, wallNanos11, cpuNanos11};
  CpuWallTiming timing17{count17, wallNanos17, cpuNanos17};
  EXPECT_EQ(count11, timing11.count);
  EXPECT_EQ(wallNanos11, timing11.wallNanos);
  EXPECT_EQ(cpuNanos11, timing11.cpuNanos);
  EXPECT_EQ(count17, timing17.count);
  EXPECT_EQ(wallNanos17, timing17.wallNanos);
  EXPECT_EQ(cpuNanos17, timing17.cpuNanos);

  timing11.add(timing17);
  EXPECT_EQ(count11 + count17, timing11.count);
  EXPECT_EQ(wallNanos11 + wallNanos17, timing11.wallNanos);
  EXPECT_EQ(cpuNanos11 + cpuNanos17, timing11.cpuNanos);

  timing17.clear();
  EXPECT_EQ(timingZero.count, timing17.count);
  EXPECT_EQ(timingZero.wallNanos, timing17.wallNanos);
  EXPECT_EQ(timingZero.cpuNanos, timing17.cpuNanos);
}

TEST_F(CpuWallTimerTest, cpuWallTimer) {
  CpuWallTiming timing;
  // Everything should be zero.
  EXPECT_EQ(0, timing.count);
  EXPECT_EQ(0, timing.wallNanos);
  EXPECT_EQ(0, timing.cpuNanos);

  constexpr std::chrono::nanoseconds sleepTime{1'100'000'000};

  {
    CpuWallTimer timer{timing};
    workAndSleep(sleepTime);
  }
  // We added a single measurement with sleep + some execution.
  EXPECT_EQ(1, timing.count);
  EXPECT_LT(sleepTime.count(), timing.wallNanos);
  EXPECT_LT(0, timing.cpuNanos);
  const auto cpuFirstTime = timing.cpuNanos;

  {
    CpuWallTimer timer{timing};
    workAndSleep(sleepTime);
  }
  // We added two measurements with sleep + some execution each.
  EXPECT_EQ(2, timing.count);
  EXPECT_LT(sleepTime.count() * 2, timing.wallNanos);
  EXPECT_LT(cpuFirstTime, timing.cpuNanos);
}

TEST_F(CpuWallTimerTest, deltaCpuWallTimer) {
  CpuWallTiming timing;
  // Everything should be zero.
  EXPECT_EQ(0, timing.count);
  EXPECT_EQ(0, timing.wallNanos);
  EXPECT_EQ(0, timing.cpuNanos);

  constexpr std::chrono::nanoseconds sleepTime{1'100'000'000};

  {
    DeltaCpuWallTimer timer{
        [&](const CpuWallTiming& deltaTiming) { timing.add(deltaTiming); }};
    workAndSleep(sleepTime);
  }
  // We added a single measurement with sleep + some execution.
  EXPECT_EQ(1, timing.count);
  EXPECT_LT(sleepTime.count(), timing.wallNanos);
  EXPECT_LT(0, timing.cpuNanos);
  const auto cpuFirstTime = timing.cpuNanos;

  {
    DeltaCpuWallTimer timer{
        [&](const CpuWallTiming& deltaTiming) { timing.add(deltaTiming); }};
    workAndSleep(sleepTime);
  }
  // We added two measurements with sleep + some execution each.
  EXPECT_EQ(2, timing.count);
  EXPECT_LT(sleepTime.count() * 2, timing.wallNanos);
  EXPECT_LT(cpuFirstTime, timing.cpuNanos);
}

} // namespace kumo::pollux::test
